classdef InertSensTriadPars
    %INERTSENSTRIADPARS 惯性器件三元组参数基类
    
    properties (Abstract, Constant)
        DetParNames % 确定性参数
        DetParDimNum
        RandParNames % 随机参数
        LinParNames % 线性确定性参数，参数间可以相加
        LinParDimNum
        PosScalFactErrName
        NegScalFactErrName
    end
    
    methods
        function obj = InertSensTriadPars(nSamp, std, mean, chooseFrom3StdAndZero)
            %INERTSENSTRIADPARS 构造此类的实例。生成标准差std，均值mean正态分布的线性确定性参数，std与mean结构体包含确定性参数域，默认为0
            if nargin < 1
                return;
            end
            if nargin < 2
                std = struct();
            end
            if nargin < 3
                mean = struct();
            end
            if nargin < 4
                chooseFrom3StdAndZero = false;
            end
            
            std = correctfields(std);
            mean = correctfields(mean);
            for i=1:length(obj.LinParNames)
                name = obj.LinParNames{i};
                nDim = obj.LinParDimNum(i);
                sz = ones(1, nDim);
                sz(1:ndims(obj.(name))) = size(obj.(name));
                sz(end) = nSamp;
                if chooseFrom3StdAndZero
                    obj.(name) = (randi(3, sz)-2) .* std.(name) * 3 + mean.(name); % 隐式展开
                else
                    obj.(name) = randn(sz) .* std.(name) + mean.(name); % 隐式展开
                end
            end
            obj = obj.resizeFieldLastDim(nSamp);
            function [s] = correctfields(s)
                for j=1:length(obj.LinParNames)
                    fName = obj.LinParNames{j};
                    if isfield(s, fName)
                        if numel(s.(fName))~=1 && any(size(s.(fName))~=size(obj.(fName))) % 当该域非标量时，尺寸必须与obj对应域相同
                            error('InertSensTriadPars:SizeMismatch', ['Size of field ' fName ' of std is not the same with that of obj']);
                        end
                    else % 若域不存在，设置默认值为0
                        s.(fName) = 0;
                    end
                end
            end
        end
        
        function obj = equalizeScalFactErr(obj, method)
            if nargin < 2
                method = 'Average';
            end
            
            switch method
                case 'UsePositive'
                    obj.(obj.NegScalFactErrName) = obj.(obj.PosScalFactErrName);
                case 'UseNegative'
                    obj.(obj.PosScalFactErrName) = obj.(obj.NegScalFactErrName);
                case 'Average'
                    obj.(obj.PosScalFactErrName) = (obj.(obj.PosScalFactErrName)+obj.(obj.NegScalFactErrName)) / 2;
                    obj.(obj.NegScalFactErrName) = obj.(obj.PosScalFactErrName);
                otherwise
                    error('InertSensTriadPars:equalizeScalFactErr', 'Invalid method');
            end
        end
        
        function nSamp = sampleNum(obj)
            %SAMPLENUM 返回确定性参数的采样数（非重复）
            nSamp = cellfun(@(par, dim)(size(obj.(par), dim)), obj.DetParNames, num2cell(obj.DetParDimNum));
            nSamp = unique(nSamp);
        end
        
        function obj = resizeFieldLastDim(obj, lastDimTargetSize)
            %RESIZEFIELDLASTDIM 调整各确定性参数最后一维的尺寸,统一至lastDimTargetSize
            for i=1:length(obj.DetParNames)
                name = obj.DetParNames{i};
                obj.(name) = resizeparlastdim(obj.(name), lastDimTargetSize, obj.DetParDimNum(i), name);
            end
        end
        
        function triadParsSum = plus(triadPars1, triadPars2)
            %PLUS 求线性确定性参数项之和
            nSamp0 = triadPars1.sampleNum();
            nSampDelta = triadPars2.sampleNum();
            if length(nSamp0)==1 && length(nSampDelta)==1
                if nSamp0>nSampDelta && nSampDelta==1
                    triadPars2 = triadPars2.resizeFieldLastDim(nSamp0);
                elseif nSamp0<nSampDelta && nSamp0==1
                    triadPars1 = triadPars1.resizeFieldLastDim(nSampDelta);
                end
            end
            
            triadParsSum = triadPars1;
            triadParsSum = triadPars1.binaryOp(triadPars2, @plus, triadParsSum);
        end
        
        function triadParsSub = minus(triadPars1, triadPars2)
            %MINUS 求线性确定性参数项之差
            triadParsSub = triadPars1 + (-triadPars2);
        end
        
        function negTriadPars = uminus(triadPars)
            %UMINUS 负的线性确定性参数项
            negTriadPars = triadPars.unaryOp(@uminus);
        end
        
        function triadParsAbs = abs(triadPars)
            %ABS 线性确定性参数项绝对值
            triadParsAbs = triadPars.unaryOp(@abs);
        end
        
        function TF = eq(triadPars1, triadPars2)
            %EQ 确定性参数项等于比较
            [~, TF] = triadPars1.binaryOp(triadPars2, @eq);
        end
        
        function TF = ne(triadPars1, triadPars2)
            %NE 确定性参数项不等于比较
            [~, TF] = triadPars1.binaryOp(triadPars2, @ne);
        end
        
        function TF = lt(triadPars1, triadPars2)
            %LT 确定性参数项小于比较
            [~, TF] = triadPars1.binaryOp(triadPars2, @lt);
        end
        
        function TF = le(triadPars1, triadPars2)
            %LE 确定性参数项小于等于比较
            [~, TF] = triadPars1.binaryOp(triadPars2, @le);
        end
        
        function TF = gt(triadPars1, triadPars2)
            %GT 确定性参数项大于比较
            [~, TF] = triadPars1.binaryOp(triadPars2, @gt);
        end
        
        function TF = ge(triadPars1, triadPars2)
            %GE 确定性参数项大于等于比较
            [~, TF] = triadPars1.binaryOp(triadPars2, @ge);
        end
        
        function objArr = convToStructArray(obj)
            %CONVTOSTRUCTARRAY 转换为nSamp*1的结构体数组，其每个元素包含一个采样
            nSamp = obj.sampleNum();
            if length(nSamp) == 1
                obj_default = eval(class(obj));
                objArr = repmat(obj_default, obj.sampleNum(), 1);
                % 提取确定性参数采样到objArr的各个元素结构体中
                for i=1:length(obj.DetParNames)
                    name = obj.DetParNames{i};
                    nDim = obj.DetParDimNum(i);
                    sz = ones(2, nDim);
                    sz(2, 1:ndims(obj.(name))) = size(obj.(name));
                    c = cell(1, nDim);
                    for j=1:nDim
                        c{j} = sz(1, j):sz(2, j);
                    end
                    for j=1:nSamp
                        c{end} = j;
                        objArr(j).(name) = obj.(name)(c{:});
                    end
                end
                % 复制随机参数值至objArr的各个元素结构体
                for i=1:length(obj.RandParNames)
                    name = obj.RandParNames{i};
                    for j=1:nSamp
                        objArr(j).(name) = obj.(name);
                    end
                end
            else
                error('InertSensTriadPars:SampNumNotUniq', 'Sample numbers of deterministic paramenters are not the same');
            end
        end
    end
    
    methods (Access = private)
        function triadParsOut = unaryOp(triadParsIn, hUnaryFcn)
            triadParsOut = triadParsIn;
            for i=1:length(triadParsOut.LinParNames)
                name = triadParsOut.LinParNames{i};
                triadParsOut.(name) = hUnaryFcn(triadParsIn.(name));
            end
        end
        
        function [triadParsOut, TF] = binaryOp(triadParsIn1, triadParsIn2, hBinaryFcn, triadParsOut)
            if nargin < 4
                triadParsOut = struct();
            end
            TF = true;
            for i=1:length(triadParsIn1.LinParNames)
                name = triadParsIn1.LinParNames{i};
                triadParsOut.(name) = hBinaryFcn(triadParsIn1.(name), triadParsIn2.(name));
                if islogical(triadParsOut.(name))
                    TF = TF & all(triadParsOut.(name), 'all');
                end
            end
        end
    end
end